Skip to content

[pull] master from cube-js:master - #638

Merged
pull[bot] merged 3 commits into
code:masterfrom
cube-js:master
Jul 30, 2026
Merged

[pull] master from cube-js:master#638
pull[bot] merged 3 commits into
code:masterfrom
cube-js:master

Conversation

@pull

@pull pull Bot commented Jul 30, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

paveltiunov and others added 3 commits July 30, 2026 13:56
…11400)

* fix(tesseract): project the requested grain into rolling-window CTEs

A `case` entrypoint dispatching between rolling-window measures on a
`type: switch` calc-group dimension could not be queried with a granular
time dimension: planning failed with "Alias not found for partition_by
dimension <calc group>".

The rolling-window CTE built its projection from the ROOT query
dimensions, while the case parent adds the switch dimension to the grain
of everything below it. The arithmetic layer above the rolling windows
(e.g. `r3 - prev_r3`) then partitions by that switch dimension and looks
it up in the CTE schema, where it was never projected. Building the CTE
schema from the description's own state fixes it and also covers the
`grain: include` widening of the same shape.

- rolling-window CTEs project `description.state()` dimensions and time
  dimensions instead of the root query's
- rust: fixture gains a `window_kind` switch dimension plus case
  entrypoints over the rolling / rolling-change measures, and a
  regression test building SQL for them with a month time dimension
- schema-compiler: month-granularity specs on the shared calc-group
  Postgres suite — the sales rollup serves the query with deterministic
  monthly values; a new model variant keys the joined cube's rollup on
  the queried time dimension and both rollups are served, with the
  rollup result asserted equal to the raw-source result

Two remaining matching limitations are pinned by the new specs:
a rollup keyed on its own (join-equal) time dimension can't serve a
query grouping by the other side's dimension, and losing one cube's
rollup drops the whole query to the raw source; and expressing the range
as an `inDateRange` filter on the time dimension instead of
`timeDimensions[].dateRange` stops every rollup from matching (skipped
FIXME spec).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CmA7TSPTdq9z83Am3e7s4u

* test(cubejs-testing): Cube Store smoke coverage for month-granularity rolling queries

Adds the month-granularity time-dimension queries to the shared calc-group
birdbox suite and keys the share_metrics rollup on the time dimension the
view exposes (`sales.date`), so the planner can match both rollups — the
cube's own `share_metrics.date` is equal by the join condition but is not a
member the matcher can resolve. The four existing no-time-dimension smoke
tests still pass with that fixture change.

The two new queries are skipped: the plan is planned and matched
correctly (covered end to end against Postgres in the schema-compiler
suite) but Cube Store cannot execute it —

  Internal: Optimizer rule 'optimize_projections' failed
  Schema error: No field named fk_aggregate.sales__r3_amount

The rolling-window CTE groups by (date, product, calc group) while its
consumer projects a strict subset of those group-by columns — dropping the
filter-pinned calc-group column but keeping the aggregate — and Cube
Store's projection optimizer prunes the aggregate out of the CTE schema. A
plain rolling measure at month granularity over the same rollup (no `case`
entrypoint, so no calc-group column in the CTE) is served by Cube Store
without trouble, and the same SQL runs on Postgres, so this is specific to
the calc-group projection shape.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CmA7TSPTdq9z83Am3e7s4u

* test(tesseract): use month-based rolling windows in the calc-group switch fixture

The switch case entrypoints dispatched between a 7-day trailing window and
the year-to-date one. A 7-day window can never be served from a rollup
stored at month granularity, so it does not match the shape these specs
stand in for. Adds a `rolling_sum_3m` (trailing 3 month) leaf and dispatches
between whole-month windows — trailing 3 month vs year-to-date — with the
switch values renamed R3 / YTD accordingly, mirroring the model in the
schema-compiler and birdbox suites.

The regression test still fails with "Alias not found for partition_by
dimension orders.window_kind" when the rolling-window CTE projection fix is
reverted.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CmA7TSPTdq9z83Am3e7s4u

* test(tesseract): cover switch/calc-group rolling windows over rollups end to end

Every shape the calc-group switch model produces is now exercised twice: served
from an external rollup in CubeStore, and straight from the source Postgres with
pre-aggregations filtered out. The two results are compared cell by cell, with
timestamp rendering normalised and numbers rounded (ratios are f64 in CubeStore
against Postgres NUMERIC, so they differ in the last digit), which pins the
rollup path as an optimization rather than a different answer. A separate
always-running test asserts rollup matching for all of them, independently of
whether an engine can execute the plan.

New fixture integration_cubestore_switch_rolling.yaml mirrors the shapes of a
production model: a `type: switch` dimension with trailing and to-date windows
behind one `case` entrypoint, an entrypoint dispatching straight onto a
`time_shift` measure, a difference and a ratio layer above the rolling windows,
grain by an own dimension and by a joined cube's, and rollups that variously
omit the calc group, store it, or carry an aggregating index. The seed carries
data from the preceding year so a 12-month trailing window and year-to-date
produce different sums — without it the two coincide and a test could not tell
them apart.

Two shapes stay ignored for CubeStore limitations unrelated to the calc-group
grain, each with the reason on the test: a four-entrypoint query builds a plan
too deep for CubeStore to decode (protobuf recursion limit), and a rollup with
an aggregating index fails at table creation with a column-type mismatch.

Also adds rolling_sum_7d_reduce_time and an executed test for it: an Aggregate
parent that reduces by the time dimension makes the child rolling-window state
narrower than the root query, and the rolling CTE must still project and group
by the rolling time dimension so the time_series join does not fan out.

* test(tesseract): address review of the switch/rolling coverage

Five points from the review, all in the test layer:

Stale docs. The note on `test_case_entrypoint` described the CubeStore
`RollingOptimizerRule` widening in the present tense, but that test is not
ignored and holds a real CubeStore snapshot — #11410 is an ancestor of this
branch and fixes exactly that rewrite. Reworded to past tense, naming the fix
and what the test now guards. The birdbox `FIXME` had the same problem: the
Cube Store side is fixed, what still blocks those two specs is that birdbox
pulls the published `cubejs/cubestore:latest` while the Cube server is built
from the working tree, so only the planner fix is in the containers under test.

Value coverage in the plain Postgres configuration. `run_both` computed the
raw-source rows and threw them away unless CubeStore was also available, so a
run without `integration-cubestore` asserted only rollup matching. It now
snapshots `normalize()` of whichever engine ran; the snapshots become
engine-independent tables instead of one engine's rendering, and the values are
pinned in an ordinary `cargo test --features integration-postgres` run.

Separator filter. `normalize()` dropped every line starting with `-`, which
would silently discard a data row whose first cell is negative — both tables
lose the same rows, so the comparison would still pass with reduced coverage.
It now drops only lines made of the separator characters.

Correlated seed columns. Each account sold exactly one category, so the
by-account snapshot was the by-category one with a column renamed and the
joined-grain test could not tell the two groupings apart. Each account now sells
both categories, and the two results genuinely differ.

Derived entrypoints on their `else` branch. Both fell back to the absolute
`ytd_amount` under measures named `..._change` and `format: percent`, so no test
covered an arithmetic layer over a to-date window — the shape closest to what
broke originally. They now dispatch to a difference and to a new `ytd_growth`,
with a test each. The seed gains early-2023 rows so the year-over-year shift has
something to subtract and those tests assert numbers rather than NULLs.

* test(tesseract): keep string cells intact when normalising results

`normalize_cell` replaced `T` and `Z` unconditionally to turn CubeStore's
`2024-05-01T00:00:00.000Z` into a space-separated timestamp, which also rewrote
string cells: the `YTD` calc-group value was recorded as `Y D` in
switch_rolling_grouped_switch.snap, so the committed artifact showed a value the
model does not declare, and the rollup-versus-source comparison lost fidelity
because both sides dropped the same characters. The substitution now matches the
timestamp shape — trailing `Z` with a `T` inside — and leaves everything else
alone.

Also fixes the seed comment: rows giving each account both categories mean the
invariant is one row per (account, category, month), not per (category, month).

* test(tesseract): unignore the aggregating-index rollup case

Building an aggregating index over a decimal measure used to panic before any
query ran — the sum widened to `Decimal128(28, 5)` against a column declared
`Decimal128(18, 5)`. Fixed in #11413, so the test runs: the rollup with an
aggregating index returns exactly what the plain one does, which is the property
worth pinning, and it is now the regression guard for that cast.

The four-entrypoint query stays ignored; a plan that deep still exceeds Cube
Store's protobuf decode recursion limit, which reproduces on a real model too.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Aleksandr Romanenko <alex.romanenko@cube.dev>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.